package cn.kinoko.service.manga.impl;

import cn.hutool.core.util.StrUtil;
import cn.kinoko.common.aspect.annotation.Cacheable;
import cn.kinoko.common.base.model.BaseServiceImpl;
import cn.kinoko.common.constant.CacheKeyConstant;
import cn.kinoko.model.manga.constant.MetricsType;
import cn.kinoko.mapper.manga.MangaMetricsMapper;
import cn.kinoko.model.manga.entity.MangaMetrics;
import cn.kinoko.model.manga.event.MangaMetricsEventArgs;
import cn.kinoko.model.manga.vo.LikeCountVO;
import cn.kinoko.service.common.event.EventTopicEnum;
import cn.kinoko.service.common.event.component.EventReceiver;
import cn.kinoko.service.manga.MangaMetricsService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.WeekFields;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/**
 * @author kinoko
 */
@Service
public class MangaMetricsServiceImpl extends BaseServiceImpl<MangaMetricsMapper, MangaMetrics> implements MangaMetricsService {

    @Resource
    private Cache<String, Object> cache;
    @Lazy
    @Resource
    private MangaMetricsService mangaMetricsService;

    // 访问量风控缓存，1小时内不可再次增加访问量
    private final Cache<String, LocalDateTime> viewCache = Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    // 点赞量风控缓存，1天内不可再次增加访问量
    private final Cache<String, LocalDateTime> likeCache = Caffeine.newBuilder()
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();


    /**
     * 浏览量+1
     *
     * @param mangaId 漫画id
     * @param uid     用户id
     * @param type    浏览量类型
     */
    @Override
    public void metricsPlus(Long mangaId, Object uid, MetricsType type) {
        // 检查风控
        if (checkRiskControl(mangaId, uid, type)) return;
        // 新增访问量
        plusMetrics(mangaId, uid, type, 1L);
        // 如果是点赞指标，增加缓存点赞量
        if (type.equals(MetricsType.LIKE)) {
            Long likeCount = mangaMetricsService.getLikeCount(mangaId);
            if (likeCount != null) {
                cache.put(CacheKeyConstant.MG_LIKE_COUNT + ":" + mangaId, likeCount + 1);
            }
        }
    }

    /**
     * 指标量-1
     *
     * @param mangaId 漫画id
     * @param uid     用户id
     * @param type    指标量类型
     */
    @Override
    public void metricsMinus(Long mangaId, Long uid, MetricsType type) {
        String key = StrUtil.format("{}:{}:{}", type.getCode(), mangaId, uid);
        // 判断是否有点点赞，如果没有则直接返回
        if (type.equals(MetricsType.LIKE) && likeCache.getIfPresent(key) == null) {
            return;
        }
        // 删除风控
        removeRiskControl(key, type);
        // 减少指标量
        plusMetrics(mangaId, uid, type, -1L);
        // 如果是点赞指标，减少缓存点赞量
        if (type.equals(MetricsType.LIKE)) {
            Long likeCount = mangaMetricsService.getLikeCount(mangaId);
            if (likeCount != null) {
                cache.put(CacheKeyConstant.MG_LIKE_COUNT + ":" + mangaId, likeCount - 1);
            }
        }
    }

    /**
     * 获取指标量和状态
     *
     * @param mangaId 漫画id
     * @param uid     用户id
     * @return 指标量
     */
    @Override
    public LikeCountVO getLikeCountAndFlag(Long mangaId, Long uid) {
        LikeCountVO likeCountVO = new LikeCountVO();
        likeCountVO.setFlag(false);
        if (uid != null) {
            String key = StrUtil.format("{}:{}:{}", MetricsType.LIKE.getCode(), mangaId, uid);
            // 查询是否存在点赞
            likeCountVO.setFlag(likeCache.getIfPresent(key) != null);
        }
        // 获取点赞数
        Long likeCount = mangaMetricsService.getLikeCount(mangaId);
        likeCountVO.setCount(likeCount);
        return likeCountVO;
    }

    /**
     * 获取指标量
     *
     * @param mangaId 漫画id
     * @return 指标量
     */
    @Cacheable(key = CacheKeyConstant.MG_LIKE_COUNT, params = "#mangaId")
    @Override
    public Long getLikeCount(Long mangaId) {
        return getBaseMapper().getLikeCount(mangaId);
    }

    /**
     * 移除风控
     *
     * @param key  key
     * @param type 指标量类型
     */
    private void removeRiskControl(String key, MetricsType type) {
        switch (type) {
            case VIEW -> viewCache.invalidate(key);
            case LIKE -> likeCache.invalidate(key);
        }
    }

    /**
     * 检查风控
     *
     * @param mangaId 漫画id
     * @param uid     用户id
     * @param type    访问量类型
     * @return true: 存在风控，false: 不存在风控
     */
    private boolean checkRiskControl(Long mangaId, Object uid, MetricsType type) {
        String key = StrUtil.format("{}:{}:{}", type.getCode(), mangaId, uid);
        return switch (type) {
            case VIEW -> checkRiskControl(key, viewCache, LocalDateTime.now().plusHours(1));
            case LIKE -> checkRiskControl(key, likeCache, LocalDateTime.now().plusDays(1));
        };
    }

    /**
     * 检查风控
     *
     * @param key        key
     * @param cache      cache
     * @param expireTime 过期时间
     * @return true: 存在风控，false: 不存在风控
     */
    private boolean checkRiskControl(String key, Cache<String, LocalDateTime> cache, LocalDateTime expireTime) {
        // 查询上次的访问时间
        LocalDateTime lastViewTime = cache.getIfPresent(key);
        // 如果存在，则还在风控时间段，直接计数不增加
        if (lastViewTime != null && lastViewTime.isAfter(LocalDateTime.now())) return true;
        // 不存在则构建，并设置1小时过期
        cache.asMap().putIfAbsent(key, expireTime);
        return false;
    }

    /**
     * 指标量增加
     *
     * @param mangaId 漫画id
     * @param uid     用户id
     * @param type    指标量类型
     * @param count   数量
     */
    private static void plusMetrics(Long mangaId, Object uid, MetricsType type, Long count) {
        LocalDateTime now = LocalDateTime.now();
        MangaMetricsEventArgs eventArgs = new MangaMetricsEventArgs();
        eventArgs.setMangaId(mangaId);
        eventArgs.setYear(now.getYear());
        eventArgs.setMonth(now.getMonthValue());
        eventArgs.setWeek(now.get(WeekFields.of(Locale.CHINA).weekOfWeekBasedYear()));
        eventArgs.setType(type);
        eventArgs.setCount(count);
        // 判断用户id类型，未登录用户是sessionID
        if (uid instanceof Long id) {
            // 登录用户id
            EventReceiver.receive(EventTopicEnum.MANGA_METRICS_PLUS, id, eventArgs);
        } else {
            // 未登录通用0表示
            EventReceiver.receive(EventTopicEnum.MANGA_METRICS_PLUS, 0L, eventArgs);
        }
    }

}